/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.core.jdbc.entity.field;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.jrose.api.annotation.persistence.Column;
import org.jrose.api.annotation.persistence.CreateTimestamp;
import org.jrose.api.annotation.persistence.PrimaryKey;
import org.jrose.api.annotation.persistence.Revision;
import org.jrose.api.annotation.persistence.UpdateTimestamp;
import org.jrose.api.common.IterableFilter;
import org.jrose.core.jdbc.entity.field.internal.ColumnField;
import org.jrose.core.jdbc.entity.field.internal.CreateTimestampField;
import org.jrose.core.jdbc.entity.field.internal.PrimaryKeyField;
import org.jrose.core.jdbc.entity.field.internal.RevisionField;
import org.jrose.core.jdbc.entity.field.internal.UpdateTimestampField;
import org.jrose.core.util.CollectionUtils;
import org.springframework.util.Assert;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Factory method for building {@link PersistenceField} instance.
 *
 * @author Xiaohai Zhang
 * @since 2013-08-17 19:20
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
abstract public class PersistenceFieldFactory {
    private static final Set<Class<? extends Annotation>> availableFieldAnnotations;

    static {
        availableFieldAnnotations = new HashSet<>();
        availableFieldAnnotations.add(PrimaryKey.class);
        availableFieldAnnotations.add(CreateTimestamp.class);
        availableFieldAnnotations.add(UpdateTimestamp.class);
        availableFieldAnnotations.add(Revision.class);
        availableFieldAnnotations.add(Column.class);
    }

    public static PersistenceField build(Class<?> entityClass, Field field) {
        Assert.notNull(entityClass, "Entity class must not be null");
        Assert.notNull(field, "Field must not be null");

        List<Annotation> fieldAnnotations = fetchAvailableFieldAnnotations(field);
        if (fieldAnnotations.isEmpty()) {
            // no available field annotation found, ignore this field
            return null;
        }
        Assert.isTrue(fieldAnnotations.size() == 1, "More than one persistence annotation presented");
        Annotation annotation = CollectionUtils.firstElement(fieldAnnotations);

        if (annotation.annotationType() == PrimaryKey.class) {
            return new PrimaryKeyField(entityClass, field, (PrimaryKey) annotation);
        } else if (annotation.annotationType() == CreateTimestamp.class) {
            return new CreateTimestampField(entityClass, field, (CreateTimestamp) annotation);
        } else if (annotation.annotationType() == UpdateTimestamp.class) {
            return new UpdateTimestampField(entityClass, field, (UpdateTimestamp) annotation);
        } else if (annotation.annotationType() == Revision.class) {
            return new RevisionField(entityClass, field, (Revision) annotation);
        } else if (annotation.annotationType() == Column.class) {
            return new ColumnField(entityClass, field, (Column) annotation);
        } else {
            throw new UnsupportedOperationException();
        }
    }

    // Private methods:

    private static List<Annotation> fetchAvailableFieldAnnotations(Field field) {
        if (ArrayUtils.isEmpty(field.getDeclaredAnnotations())) {
            return Collections.emptyList();
        }
        return CollectionUtils.filterIterable(Arrays.asList(field.getDeclaredAnnotations()), new IterableFilter<Annotation>() {
            @Override
            public boolean accept(Annotation element) {
                return availableFieldAnnotations.contains(element.annotationType());
            }
        });
    }
}
